/*
 * Copyright 2010 jun.enomoto<jun.enomoto@gmail.com>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package foursquare4j;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.xml.sax.SAXException;

import foursquare4j.exception.FoursquareException;
import foursquare4j.type.CheckinResult;
import foursquare4j.type.Checkins;
import foursquare4j.type.Friends;
import foursquare4j.type.Requests;
import foursquare4j.type.Response;
import foursquare4j.type.Settings;
import foursquare4j.type.Tip;
import foursquare4j.type.TipsResult;
import foursquare4j.type.User;
import foursquare4j.type.Users;
import foursquare4j.type.Venue;
import foursquare4j.type.Venues;
import foursquare4j.xml.handler.CheckinHandler;
import foursquare4j.xml.handler.CheckinsHandler;
import foursquare4j.xml.handler.FriendsHandler;
import foursquare4j.xml.handler.Handler;
import foursquare4j.xml.handler.HistoryHandler;
import foursquare4j.xml.handler.RequestsHandler;
import foursquare4j.xml.handler.ResponseHandler;
import foursquare4j.xml.handler.SettingsHandler;
import foursquare4j.xml.handler.TipHandler;
import foursquare4j.xml.handler.TipsResultHandler;
import foursquare4j.xml.handler.UserHandler;
import foursquare4j.xml.handler.UsersHandler;
import foursquare4j.xml.handler.VenueHandler;
import foursquare4j.xml.handler.VenuesHandler;

public abstract class FoursquareBase implements Foursquare {

	private static final Logger logger = Logger.getLogger(FoursquareBase.class.getName());

	protected static enum HttpMethod {
		POST, GET
	}

	protected String userAgent = Foursquare.USER_AGENT;

	public void setUserAgent(final String userAgent) {
		this.userAgent = userAgent;
	}

	public Checkins checkins(final String geolat, final String geolong) throws FoursquareException {
		final Map<String, String> parameters = new Parameters();
		parameters.put("geolat", geolat);
		parameters.put("geolong", geolong);
		return execute(HttpMethod.GET, Foursquare.API_CHECKINS_URL, parameters, new CheckinsHandler());
	}

	public CheckinResult checkin(final String vid, final String venue, final String shout, final String _private, final String twitter, final String facebook, final String geolat, final String geolong) throws FoursquareException {
		final Map<String, String> parameters = new Parameters();
		parameters.put("vid", vid);
		parameters.put("venue", venue);
		parameters.put("shout", shout);
		parameters.put("private", _private);
		parameters.put("twitter", twitter);
		parameters.put("facebook", facebook);
		parameters.put("geolat", geolat);
		parameters.put("geolong", geolong);
		return execute(HttpMethod.POST, Foursquare.API_CHECKIN_URL, parameters, new CheckinHandler());
	}

	public Checkins history(final String l, final String sinceid) throws FoursquareException {
		final Map<String, String> parameters = new Parameters();
		parameters.put("l", l);
		parameters.put("sinceid", sinceid);
		return execute(HttpMethod.GET, Foursquare.API_HISTORY_URL, parameters, new HistoryHandler());
	}

	public User user(final String uid, final String badges, final String mayor) throws FoursquareException {
		final Map<String, String> parameters = new Parameters();
		parameters.put("uid", uid);
		parameters.put("badges", badges);
		parameters.put("mayor", mayor);
		return execute(HttpMethod.GET, Foursquare.API_USER_URL, parameters, new UserHandler());
	}

	public Friends friends(final String uid) throws FoursquareException {
		final Map<String, String> parameters = new Parameters();
		parameters.put("uid", uid);
		return execute(HttpMethod.GET, Foursquare.API_FRIENDS_URL, parameters, new FriendsHandler());
	}

	public Venues venues(final String geolat, final String geolong, final String l, final String q) throws FoursquareException {
		final Map<String, String> parameters = new Parameters();
		parameters.put("geolat", geolat);
		parameters.put("geolong", geolong);
		parameters.put("l", l);
		parameters.put("q", q);
		return execute(HttpMethod.GET, Foursquare.API_VENUES_URL, parameters, new VenuesHandler());
	}

	public Venue venue(final String vid) throws FoursquareException {
		final Map<String, String> parameters = new Parameters();
		parameters.put("vid", vid);
		return execute(HttpMethod.GET, Foursquare.API_VENUE_URL, parameters, new VenueHandler());
	}

	public Venue addvenue(final String name, final String address, final String crossstreet, final String city, final String state, final String zip, final String phone, final String geolat, final String geolong) throws FoursquareException {
		final Map<String, String> parameters = new Parameters();
		parameters.put("name", name);
		parameters.put("address", address);
		parameters.put("crossstreet", crossstreet);
		parameters.put("city", city);
		parameters.put("state", state);
		parameters.put("zip", zip);
		parameters.put("phone", phone);
		parameters.put("geolat", geolat);
		parameters.put("geolong", geolong);
		return execute(HttpMethod.POST, Foursquare.API_ADDVENUE_URL, parameters, new VenueHandler());
	}

	public Venue proposeedit(final String vid, final String name, final String address, final String crossstreet, final String city, final String state, final String zip, final String phone, final String geolat, final String geolong) throws FoursquareException {
		final Map<String, String> parameters = new Parameters();
		parameters.put("vid", vid);
		parameters.put("name", name);
		parameters.put("address", address);
		parameters.put("crossstreet", crossstreet);
		parameters.put("city", city);
		parameters.put("state", state);
		parameters.put("zip", zip);
		parameters.put("phone", phone);
		parameters.put("geolat", geolat);
		parameters.put("geolong", geolong);
		return execute(HttpMethod.POST, Foursquare.API_PROPOSEEDIT_URL, parameters, new VenueHandler());
	}

	public Response flagclosed(final String vid) throws FoursquareException {
		final Map<String, String> parameters = new Parameters();
		parameters.put("vid", vid);
		return execute(HttpMethod.POST, Foursquare.API_FLAGCLOSED_URL, parameters, new ResponseHandler());
	}

	public TipsResult tips(final String geolat, final String geolong, final String l) throws FoursquareException {
		final Map<String, String> parameters = new Parameters();
		parameters.put("geolat", geolat);
		parameters.put("geolong", geolong);
		parameters.put("l", l);
		return execute(HttpMethod.GET, Foursquare.API_TIPS_URL, parameters, new TipsResultHandler());
	}

	public Tip addtip(final String vid, final String text, final String type, final String geolat, final String geolong) throws FoursquareException {
		final Map<String, String> parameters = new Parameters();
		parameters.put("vid", vid);
		parameters.put("text", text);
		parameters.put("type", type);
		parameters.put("geolat", geolat);
		parameters.put("geolong", geolong);
		return execute(HttpMethod.POST, Foursquare.API_ADDTIP_URL, parameters, new TipHandler());
	}

	public Tip tipMarktodo(final String tid) throws FoursquareException {
		final Map<String, String> parameters = new Parameters();
		parameters.put("tid", tid);
		return execute(HttpMethod.POST, Foursquare.API_TIP_MARKTODO_URL, parameters, new TipHandler());
	}

	public Tip tipMarkdone(final String tid) throws FoursquareException {
		final Map<String, String> parameters = new Parameters();
		parameters.put("tid", tid);
		return execute(HttpMethod.POST, Foursquare.API_TIP_MARKDONE_URL, parameters, new TipHandler());
	}

	public Requests friendRequests() throws FoursquareException {
		return execute(HttpMethod.GET, Foursquare.API_FRIEND_REQUESTS_URL, Collections.<String, String> emptyMap(), new RequestsHandler());
	}

	public User friendApprove(final String uid) throws FoursquareException {
		final Map<String, String> parameters = new Parameters();
		parameters.put("uid", uid);
		return execute(HttpMethod.POST, Foursquare.API_FRIEND_APPROVE_URL, parameters, new UserHandler());
	}

	public User friendDeny(final String uid) throws FoursquareException {
		final Map<String, String> parameters = new Parameters();
		parameters.put("uid", uid);
		return execute(HttpMethod.POST, Foursquare.API_FRIEND_DENY_URL, parameters, new UserHandler());
	}

	public User friendSendrequest(final String uid) throws FoursquareException {
		final Map<String, String> parameters = new Parameters();
		parameters.put("uid", uid);
		return execute(HttpMethod.POST, Foursquare.API_FRIEND_SENDREQUEST_URL, parameters, new UserHandler());
	}

	public Users findFriendsByname(final String q) throws FoursquareException {
		final Map<String, String> parameters = new Parameters();
		parameters.put("q", q);
		return execute(HttpMethod.GET, Foursquare.API_FINDFRIENDS_BYNAME_URL, parameters, new UsersHandler());
	}

	public Users findFriendsByphone(final String q) throws FoursquareException {
		final Map<String, String> parameters = new Parameters();
		parameters.put("q", q);
		return execute(HttpMethod.GET, Foursquare.API_FINDFRIENDS_BYPHONE_URL, parameters, new UsersHandler());
	}

	public Users findFriendsBytwitter(final String q) throws FoursquareException {
		final Map<String, String> parameters = new Parameters();
		parameters.put("q", q);
		return execute(HttpMethod.GET, Foursquare.API_FINDFRIENDS_BYTWITTER_URL, parameters, new UsersHandler());
	}

	public Settings settingsSetpings(final String uid, final String pings) throws FoursquareException {
		final Map<String, String> parameters = new Parameters();
		parameters.put(uid, pings);
		return execute(HttpMethod.POST, Foursquare.API_SETTINGS_SETPINGS_URL, parameters, new SettingsHandler());
	}

	public Response test() throws FoursquareException {
		return execute(HttpMethod.GET, Foursquare.API_TEST_URL, Collections.<String, String> emptyMap(), new ResponseHandler());
	}

	protected abstract <T> T execute(final HttpMethod method, final String url, final Map<String, String> parameters, final Handler<T> handler) throws FoursquareException;

	protected <T> T parseBody(InputStream body, final Handler<T> handler) throws ParserConfigurationException, SAXException, IOException {
		if (body == null) return null;

		if (logger.isLoggable(Level.FINE)) {
			ByteArrayOutputStream baos = new ByteArrayOutputStream();
			byte[] buff = new byte[1024];
			int size = 0;
			while ((size = body.read(buff, 0, buff.length)) > -1)
				baos.write(buff, 0, size);

			byte[] data = baos.toByteArray();
			logger.fine(new String(data, "UTF-8"));
			body = new ByteArrayInputStream(data);
		}

		final SAXParser parser = SAXParserFactory.newInstance().newSAXParser();
		parser.parse(body, handler);
		return handler.getObject();
	}

	private static class Parameters extends LinkedHashMap<String, String> {

		private static final long serialVersionUID = 1L;

		@Override
		public String put(String key, String value) {
			if (value == null) return null;
			return super.put(key, value);
		}
	}
}